iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 14 篇

Day 14|data.customer.name 為什麼會突然噴錯?巢狀資料到底在防什麼?

  • 分享至 

  • xImage
  •  

前一篇講到:

?.
??
||
? :

其中 ?. 很常跟 API Response 一起出現。

例如:

data?.customer?.name

以前我看到這種寫法,會覺得:

為什麼要一直加問號?

後來才發現,真正的問題不是 name 有沒有值。

而是:

中間任何一層都有可能根本不存在。


先看一個最簡單的巢狀資料

假設 API 回:

const data = {
  customer: {
    name: "ABC Company",
  },
};

如果要拿公司名稱:

data.customer.name

流程其實是:

data
↓
customer
↓
name

結果:

ABC Company

這時候完全沒問題。


問題出在中間資料可能不存在

假設 API 改成:

const data = {
  customer: null,
};

如果還寫:

data.customer.name

就會出錯。

因為程式其實在做:

data
↓
customer
↓
null
↓
還想繼續拿 .name

但:

null.name

是不成立的。

所以 JavaScript 會直接丟 Error。

這和:

customer.name

最後的 name 是不是 undefined,

其實是兩件不同的事。


?. 是在中間幫忙踩煞車

改成:

data.customer?.name

意思可以先理解成:

customer 如果存在,就繼續拿 name;如果 customer 是 null 或 undefined,就停下來。

所以:

data.customer?.name

在:

customer = null

時,不會噴錯。

而是得到:

undefined

流程:

data
↓
customer
↓
null
↓
?. 發現不能繼續
↓
停止
↓
undefined

如果連 data 本身都有可能不存在呢?

例如 API 還沒回來之前:

data = undefined;

這時候:

data.customer?.name

一樣會出錯。

因為第一步:

data.customer

就已經不成立。

所以要寫:

data?.customer?.name

意思就是:

data 有嗎?
↓
有才繼續

customer 有嗎?
↓
有才繼續

最後拿 name

只要其中一層是:

null
undefined

就停止。


為什麼 API 很常出現這種狀況?

因為實際 API Response 不一定每一筆資料結構都完整。

例如:

const order = {
  customer: {
    company: {
      name: "ABC",
    },
  },
};

可能另一筆資料是:

const order = {
  customer: {
    company: null,
  },
};

甚至:

const order = {
  customer: null,
};

所以:

order.customer.company.name

其實是在假設:

customer 一定存在
company 一定存在
name 一定存在

只要其中一個假設不成立,程式就會壞掉。


Optional Chaining 可以一路保護下去

所以常看到:

order?.customer?.company?.name

可以翻成人話:

order 有嗎?
↓
customer 有嗎?
↓
company 有嗎?
↓
name

如果其中一層不存在:

停止
↓
回傳 undefined

而不是直接噴錯。


但 ?. 不代表資料一定正確

這也是很重要的一點。

例如:

order?.customer?.company?.name

如果最後得到:

undefined

只能代表:

某一層沒有值。

但不能直接知道是哪一層。

可能是:

order 不存在

也可能是:

customer 不存在

也可能是:

company 不存在

也可能只是:

name 沒有值

所以如果真的在 Debug,

不能只看到:

undefined

就結束。

還是要逐層確認。


實際 Debug 我會這樣拆

例如畫面:

order?.customer?.company?.name ?? "N/A"

結果顯示:

N/A

以前可能直接想:

公司名稱沒有資料。

但其實還不知道是哪裡斷掉。

我會開始看:

console.log(order);
console.log(order?.customer);
console.log(order?.customer?.company);
console.log(order?.customer?.company?.name);

逐層確認:

order 有嗎?
↓
customer 有嗎?
↓
company 有嗎?
↓
name 有嗎?

這樣才能真的知道問題在哪。


?. 和 ?? 很常一起用

例如:

order?.customer?.company?.name ?? "N/A"

這兩個符號其實各自負責不同工作。

?.

負責:

安全地往巢狀資料裡面拿。

而:

??

負責:

如果最後得到 null / undefined,就使用預設值。

所以整句可以翻成:

先安全地找:
order
↓
customer
↓
company
↓
name

如果最後沒有值
↓
顯示 N/A

但 ?. 不會幫你處理所有 falsy 值

例如:

const data = {
  amount: 0,
};

寫:

data?.amount

結果仍然是:

0

因為 ?. 只在前面的值是:

null
undefined

時停止。

它不會把:

0
""
false

當成不存在。

這點跟上一篇的 || 很不一樣。


巢狀資料其實是在表示「資料裡面還有資料」

例如:

const user = {
  name: "小潔",
  company: {
    name: "ABC",
    address: {
      city: "Taipei",
    },
  },
};

可以畫成:

user
│
├─ name
│
└─ company
    │
    ├─ name
    │
    └─ address
        │
        └─ city

所以:

user.company.address.city

就是一路往裡面找:

user
↓
company
↓
address
↓
city

這就是所謂的:

Nested Data

巢狀資料。


API Response 很常是巢狀的

例如:

{
  "id": 123,
  "customer": {
    "id": 10,
    "name": "ABC Company"
  }
}

前端拿:

data.customer.name

這很常見。

但也可能 API 回的是:

{
  "id": 123,
  "customer": null
}

這時候就需要考慮:

data.customer?.name

而不是直接假設:

customer 永遠存在

為什麼有時候下拉選單也會特別處理巢狀資料?

實際專案裡很常遇到:

API 回來的資料不是:

{
  label: "ABC",
  value: 1,
}

而是:

{
  id: 1,
  customer: {
    name: "ABC",
  },
}

但 Select 可能需要:

{
  label: "ABC",
  value: 1,
}

那前端就會需要轉換:

const options = data.map(item => ({
  label: item.customer?.name ?? "N/A",
  value: item.id,
}));

流程就是:

API 原始資料
↓
取巢狀欄位
↓
整理成 UI Component 需要的格式
↓
給 Select 使用

所以很多時候前端不是單純:

API 回什麼,我就直接顯示什麼。

還需要把資料重新整理成 UI 需要的形狀。


看到很多 ?.,也不要直接全部照抄

例如:

data?.customer?.company?.address?.city

雖然很安全,

但也要想一下:

這些欄位真的都可能不存在嗎?

如果按照 TypeScript 型別或 API Contract:

data 一定存在
customer 一定存在
company 才是 optional

那可能只需要:

data.customer.company?.name

如果什麼地方都亂加:

?.

雖然不一定出錯,

但也可能把原本應該暴露出來的資料問題悄悄吞掉。

所以 ?. 的目的不是:

防越多越好。

而是:

在「真的可能是 null / undefined」的地方安全處理。


我現在看到巢狀資料,會先畫結構

例如看到:

data.customer.company.name

我會先拆成:

data
↓
customer
↓
company
↓
name

然後問:

哪一層可能是 null?
哪一層可能是 undefined?
API Contract 怎麼定義?

再決定:

?.

要放在哪裡。

這比看到 Error 之後直接:

data?.customer?.company?.name

全部加滿,更容易真的理解資料。


今天最想記住的是

Nested Data
→ 資料裡面還包著其他資料

而:

?.

是在:

中間某一層可能是 null / undefined 時,安全停止取值。

例如:

data?.customer?.name

不是在問:

name 有沒有內容?

而是在確保:

拿 name 之前
↓
data 跟 customer 至少可以繼續往下取

最後再搭配:

??

像:

data?.customer?.name ?? "N/A"

就是:

安全取得 name
↓
真的沒有值
↓
顯示 N/A

下一篇可以繼續接巢狀資料另一個很常見的實務問題:

API 回來的資料形狀跟 UI 要的不一樣時,為什麼前端常常要 map() 重新整理資料?


上一篇
Day 13|||、??、三元運算子都像在給預設值,到底怎麼選?
下一篇
Day 15|API 回來的資料不能直接用?map() 其實是在幫 UI 整理資料
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言